﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Reflection;

namespace JAAF.Builders
{
    /// <summary>
    /// Defines the base class for concrete builder classes
    /// </summary>
    public abstract class BuilderBase
    {
        protected class RegistrationInfo
        {
            public Type Abstraction;
            public string AbstractionName;

            public Type Implementation;
            public string ImplementationTypeName;
            //public string ImplementationAssemblyName;

            public InstancingPolicyEnum Instancing;
        }

        #region Instance Data

        private BuilderBase m_oFallbackBuilder;
        
        private RegistrationInfo m_oDefaultRegistration;

        private Dictionary<string, RegistrationInfo> m_dicRegistrations = new Dictionary<string, RegistrationInfo>();

        /// <summary>
        /// Dependency object instances that are shared among all invocations
        /// </summary>
        private Dictionary<string, object> m_dicSingletons = new Dictionary<string, object>();


        #endregion


        #region Initialization

        /// <summary>
        /// Creates a new instance of a Builder
        /// </summary>
        /// <param name="_oParentBuilder">A higher level builder that can be used to resolve types not handled by this builder</param>
        protected BuilderBase(BuilderBase _oFallbackBuilder = null)
        {
            m_oFallbackBuilder = _oFallbackBuilder;
        }
        
        #endregion


        #region Methods


        #region Creation

        /// <summary>
        /// Creates and returns a concrete object for the default registration.
        /// </summary>
        /// <param name="_aoConstructorParams">Constructor parameters tor the implementation object.</param>
        /// <returns>An instance of the implementation.</returns>
        public object Create(params object[] _aoConstructorParams)
        {
            string sAbstraction = null;
            
            if (m_oDefaultRegistration != null)
            {
                if (m_oDefaultRegistration.AbstractionName != null)
                    sAbstraction = m_oDefaultRegistration.AbstractionName;
                else if (m_oDefaultRegistration.Abstraction != null)
                    sAbstraction = m_oDefaultRegistration.Abstraction.FullName;
            }

            if (sAbstraction == null)
                throw new InvalidOperationException("There is no default implementation registered to create."); //RESOURCE

            return this.Create(sAbstraction, _aoConstructorParams);
            
        }

        /// <summary>
        /// Creates and returns a concrete implementation of a requested abstraction.
        /// </summary>
        /// <param name="_oAbstractionType">The type of the abstraction.</param>
        /// <param name="_aoConstructorParams">Constructor parameters tor the implementation object.</param>
        /// <returns>An instance of the implementation.</returns>
        public object Create(Type _oAbstractionType = null, params object[] _aoConstructorParams)
        {
            return this.Create(_oAbstractionType != null? _oAbstractionType.FullName : null, _aoConstructorParams);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_aoConstructorParams"></param>
        /// <returns>An instance of the implementation.</returns>
        public T Create<T>(params object[] _aoConstructorParams)
        {
            return (T)this.Create(typeof(T).FullName, _aoConstructorParams);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_sAbstraction"></param>
        /// <param name="_aoConstructorParams"></param>
        /// <returns>An instance of the implementation.</returns>
        public virtual object Create(string _sAbstraction = null, params object[] _aoConstructorParams)
        {
            object oResult = null;

            // Look for any registered instances
            if (m_dicSingletons.TryGetValue(_sAbstraction, out oResult))
            {
                return oResult;
            }

            RegistrationInfo oReg = null;

            // If no abstraction argument is passed, look for a default registration
            if (_sAbstraction == null)
            {
                if (m_oDefaultRegistration == null)
                {
                    if (m_oFallbackBuilder != null)
                        return m_oFallbackBuilder.Create(_sAbstraction, _aoConstructorParams);
                    else
                        throw new MissingRegistrationException(_sAbstraction);
                }

                oReg = m_oDefaultRegistration;
            }
            else if (!m_dicRegistrations.TryGetValue(_sAbstraction, out oReg))
            {
                if (m_oFallbackBuilder == null)
                    throw new MissingRegistrationException(_sAbstraction);
                else
                    return m_oFallbackBuilder.Create(_sAbstraction, _aoConstructorParams);
            }

            if (oReg.Implementation == null)
                oReg.Implementation = this.LoadType(oReg.ImplementationTypeName);

            //TODO: Should be able to call parametrized constructors and provide values to parameters that are
            //      registered as abstractions

            oResult = this.Construct(oReg.Implementation, _aoConstructorParams);
            
            if (oReg.Instancing == InstancingPolicyEnum.Singleton)
                this.RegisterSingleton(oReg.AbstractionName, oResult);

            return oResult;
        }

        #endregion


        #region Registration

        /// <summary>
        /// 
        /// </summary>
        /// <remarks>
        /// Used only internally for loading directly from the configuration
        /// </remarks>
        /// <param name="_sAbstraction"></param>
        /// <param name="_sImplementationAssemblyName"></param>
        /// <param name="_eInstancing"></param>
        internal void RegisterImplementation(string _sAbstraction,
                                             string _sImplementationTypeName,
                                             InstancingPolicyEnum _eInstancing = InstancingPolicyEnum.PerCall)
        {
            if (m_dicRegistrations.ContainsKey(_sAbstraction))
                m_dicRegistrations.Remove(_sAbstraction);

            RegistrationInfo oReg = new RegistrationInfo()
                                        {
                                            AbstractionName = _sAbstraction,
                                            ImplementationTypeName = _sImplementationTypeName,
                                        };


            if (m_dicRegistrations.Count == 0)
                m_oDefaultRegistration = oReg;

            m_dicRegistrations.Add(_sAbstraction, oReg);

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_sAbstraction"></param>
        /// <param name="_oImplementingType"></param>
        /// <param name="_eInstancing"></param>
        internal void RegisterImplementation(string _sAbstraction,
                                             Type _oImplementingType,
                                             InstancingPolicyEnum _eInstancing = InstancingPolicyEnum.PerCall)
        {
            if (m_dicRegistrations.ContainsKey(_sAbstraction))
                m_dicRegistrations.Remove(_sAbstraction);

            RegistrationInfo oReg = new RegistrationInfo()
            {
                AbstractionName = _sAbstraction,
                Implementation = _oImplementingType,
                Instancing = _eInstancing
            };


            if (m_dicRegistrations.Count == 0)
                m_oDefaultRegistration = oReg;

            m_dicRegistrations.Add(_sAbstraction, oReg);
        }

        /// <summary>
        /// Registers an implementation for an abstract type / interface.
        /// </summary>
        /// <typeparam name="TAbstraction">The abstract type or interface</typeparam>
        /// <typeparam name="TImplementation">The <see cref="System.Type"/> that implements the abstraction.</typeparam>
        /// <param name="_eInstancing">Defines the <see cref="InstancingPolicyEnum">instancing policy</see> of the implementing type.</param>
        public void RegisterImplementation<TAbstraction, TImplementation>(InstancingPolicyEnum _eInstancing = InstancingPolicyEnum.PerCall)
        {
            this.RegisterImplementation(typeof(TAbstraction), typeof(TImplementation));
        }

        /// <summary>
        /// Registers an implementation for an abstract type / interface.
        /// </summary>
        /// <param name="_oAbstractionType">The abstract type or interface</param>
        /// <param name="_oImplementationType">The type that implements the abstraction.</param>
        /// <param name="_eInstancing">Defines the <see cref="InstancingPolicyEnum">instancing policy</see> of the implementing type.</param>
        public void RegisterImplementation(Type _oAbstractionType,
                                           Type _oImplementationType,
                                           InstancingPolicyEnum _eInstancing = InstancingPolicyEnum.PerCall)
        {
            if (m_dicRegistrations.ContainsKey(_oAbstractionType.FullName))
                m_dicRegistrations.Remove(_oAbstractionType.FullName);

            RegistrationInfo oReg = new RegistrationInfo()
            {
                Abstraction = _oAbstractionType,
                Implementation = _oImplementationType,
                Instancing = _eInstancing
            };

            if (m_dicRegistrations.Count == 0)
                m_oDefaultRegistration = oReg;

            m_dicRegistrations.Add(_oAbstractionType.FullName, oReg);
        }

        /// <summary>
        /// Registers an instance of an object that implements an abstraction.
        /// This exact instance will be injected whenever the abstraction is referenced.
        /// </summary>
        /// <param name="_oAbstractionType">The abstract type or interface</param>
        /// <param name="_oImplementation">An instance of an object that implements the abstraction.</param>
        public void RegisterInstance(Type _oAbstractionType, object _oImplementation)
        {
            this.RegisterSingleton(_oAbstractionType.FullName, _oImplementation);
        }

        /// <summary>
        /// Checks if an implementation for an abstraction is registered with this builder
        /// </summary>
        /// <param name="_oAbstractionType">The <see cref="System.Type">type</see> of the abstraction.</param>
        /// <returns>True if an implementation is registered, otherwise false.</returns>
        public bool IsRegistered(Type _oAbstractionType)
        {
            return this.IsRegistered(_oAbstractionType.FullName);
        }

        /// <summary>
        /// Checks if an implementation for an abstraction is registered with this builder
        /// </summary>
        /// <param name="_sAbstractionName">The name of the abstraction</param>
        /// <returns>True if an implementation is registered, otherwise false.</returns>
        public bool IsRegistered(string _sAbstractionName)
        {
            if (m_dicRegistrations.ContainsKey(_sAbstractionName) || m_dicSingletons.ContainsKey(_sAbstractionName))
                return true;
            else
                return false;
        }

        #endregion


        #region Helpers

        /// <summary>
        /// Registers a global instance of an object that implements an abstraction.
        /// </summary>
        /// <param name="_sAbstraction">The abstraction that is being implemented.</param>
        /// <param name="_oSigleInstance">The singleton instance.</param>
        protected void RegisterSingleton(string _sAbstraction, object _oSigleInstance)
        {
            lock (m_dicSingletons)
            {
                if (m_dicSingletons.ContainsKey(_sAbstraction))
                {
                    m_dicSingletons.Remove(_sAbstraction);
                    m_dicSingletons.Add(_sAbstraction, _oSigleInstance);
                }
            }
        }

        /// <summary>
        /// This method manages the location and loading of a implementation's assembly
        /// and type definition.
        /// </summary>
        /// <param name="_sImplTypeName">The full name of the implementation's type, including the assembly name (full or partial).</param>
        /// <returns></returns>
        protected virtual Type LoadType(string _sImplTypeName)
        {
            Type oLoadedType = null;
            
            oLoadedType = Type.GetType(_sImplTypeName, true, true);

            return oLoadedType;
        }

        /// <summary>
        /// This method is responsible for executing a type's constructor and returning the new instance
        /// </summary>
        /// <param name="_oTypeToConstruct">The type to construct</param>
        /// <param name="_aoConstructorParams">Optional constructor parameters</param>
        /// <returns></returns>
        protected abstract object Construct(Type _oTypeToConstruct, params object[] _aoConstructorParams);

        #endregion

        #endregion


    }
}
